# Some handy methods for mocking records according to auto_rest defaults
module AutoRest
  module SpecsHelper

    # Return a default value given a type.
    def ar_mock_default_value_for_type type
      case type
      when :int, :integer               then "\"1\""
      when :float                       then "\"1.5\""
      when :decimal                     then "\"9.99\""
      when :datetime, :timestamp, :time then "Time.now"
      when :date                        then "Date.today"
      when :string                      then "\"MyString\""
      when :text                        then "\"MyText\""
      when :boolean                     then "false"
      else
        ""
      end      
    end

    # Fake model reponses for a given Model Class. 
    # Can take into account AutoRest data.
    # Options:
    # :controller => ControllerClass
    # :action => :edit _or_ :index _or_ ...                   (to mock the specified auto_rest fields)
    # :lock_version => true _or_ false                        (to mock lock version attributes)
    # :mock_also => :attribute _or_ %|attr1 attr2 attr3 ...|  (to mock other methods, each mock returns a string) 
    def ar_fake_model model_klass, options= {}
      
      # Only use the controller if ardata is present
      controller= options[:controller]
      controller= nil unless controller && controller.respond_to?(:ardata)
      
      lock_version= options[:lock_version]
      
      # Build collection of aditional methods to mock
      other_methods_to_mock= [options[:mock_also]]
      action= options[:action]
      
      # Try to mock the fields.for_[...] methods and hidden_fields.
      if action && controller
        methods_collection= controller.ardata.fields.send("for_#{action}")
        methods_collection+= controller.ardata.hidden_fields.to_a if action == :edit || action == :new
        
        methods_collection.each do |attr, _title|
          other_methods_to_mock << attr.to_s if String === attr || Symbol === attr
        end
      end
      
      # To compare with model.column_names all elements must be strings. Also remove dupes.
      other_methods_to_mock.flatten!
      other_methods_to_mock.reject!{ |elem| elem.nil? }
      other_methods_to_mock= other_methods_to_mock.map{|e|e.to_s}.uniq 
      
      # Now begin the mock
      returning(mock_model(model_klass)) do |record|
        
        record.stub!(:label).and_return("Specific #{model_klass.class_name.humanize}")
        record.stub!(:column_for_attribute).and_return(:string)
        record.stub!(:errors).and_return(stub(Array, :on => nil, :count => 0))
        
        # Mock the lock_version related methods.
        if lock_version
          record.stub!(:lock_version).and_return(1) 
          record.should_receive(:"has_attribute?").with(:lock_version).and_return(true)
        end

        # We don't need to stub the id of the record
        model_attributes= model_klass.columns.reject {|col| col.name == "id" }
        
        # Stub (1) The attributes (2) the foreing relations (3) Other requested methods
        model_attributes.each do |col|
          record.stub!(col.name).and_return(ar_mock_default_value_for_type(col.type)) # (1)
          
          if controller # I need an auto_rest enabled controller for the following.
            mod_for_att= controller.ardata.foreing_models[col.name]

            if mod_for_att # (2)
              parent_mock= mock_model(mod_for_att) 
              parent_mock.stub!(:label).and_return("Foreing #{mod_for_att.class_name.humanize}.")
              record.stub!(mod_for_att.class_name.underscore.intern).and_return(parent_mock)
            end
          end
        end
        
        # Mock the rest of the methods if they haven't been stubed.
        (other_methods_to_mock - model_attributes.map {|col| col.name}).each do |name| # (3)
          record.should_receive(name).and_return("Fake value for #{name}.")
        end
      end
    end

    # Fake collections of models of given Model Class.
    # options:
    # :pages => mock the page_count used by will_paginate
    # :num_records => quantity of records in the collection. Defaults to 2.
    def ar_fake_model_collection model_klass, options= {}
      
      pages= options.delete :pages
      num_records= options.delete(:num_records) || 2
      
      returning([]) do |collection|
        
        1.upto(num_records) do
          collection << ar_fake_model(model_klass, options)
        end
        
        # Fake the page count also.
        if pages || model_klass.respond_to?(:per_page)
          collection.stub!(:page_count).and_return(pages || (num_records / model_klass.per_page).to_i)
        end
      end
    end
    
    # Call of a generic "with_tag" method inside a form spec on views. 
    # Needs ardata of the controller
    def ar_spec_form_tag_of_attribute(attr, title, ardata)
      return unless tipe= ardata.resource.model.columns_hash[attr.to_s].instance_variable_get("@type")
      return unless String === title || Symbol === title

      # Spect to find a select if attr. is a foreing key, or an input if it is a normal field.
      if ardata.foreing_models.keys.include? attr.to_s
        with_tag("select#resource_#{attr}[name=?]", "resource[#{attr}]")
      else
        with_tag("input#resource_#{attr}[value=?][name=?]", 
          CGI::escapeHTML(ar_mock_default_value_for_type(tipe)), "resource[#{attr}]")
      end
      
      # Find the title with any formatting
      response.body.should match(Regexp.new(title)) if String === title
    end
    
    # Match of mocked attribute value on the response of index and show.
    def ar_spec_attribute_value(attr, title, ardata)
      return unless String === attr  
      
      col_hash= ardata.resource.model.columns_hash[attr.to_s]
      return unless tipe= col_hash.instance_variable_get("@type") # To avoid deprec. msg
      
      response.body.should match(Regexp.new(title))
      
      # There's a difference when the attribute is a foreing model or not.
      if ardata.foreing_models.keys.include? attr.to_s
        label= "Foreing #{ardata.foreing_models[attr.to_s].class_name.humanize}."
        response.body.should match(Regexp.new(label))
      else
        response.body.should match(Regexp.new("Fake value for #{attr}."))
      end
    end

    # Every foreing model that auto_rest is aware of on a given controller
    # will instantiate a collection of records to select one from.
    # Fake that collection.
    # options:
    #   :controller => ControllerClass
    #   :action => :edit | :new | etc...
    def ar_mock_foreing_models options= {}
      return unless options[:controller] && options[:controller].respond_to?(:ardata)

      ardata= options[:controller].ardata
      action= options[:action]

      ardata.foreing_models.each do |attribute, model_klass|
        if ardata.fields.send("for_#{action}").include? attribute
          model_klass.should_receive(:find).with(any_args).and_return(ar_fake_model_collection(model_klass))
        end
      end
    end    
  end
end    